/* Copyright 2011 Google Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * Project home page: http://code.google.com/p/usb-serial-for-android/
 */

package com.stu.usb;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;

import java.util.Map;

/**
 * Helper class which finds compatible {@link UsbDevice}s and creates
 * {@link UsbSerialDriver} instances.
 *
 * <p/>
 * You don't need a Prober to use the rest of the library: it is perfectly
 * acceptable to instantiate driver instances manually. The Prober simply
 * provides convenience functions.
 *
 * <p/>
 * For most drivers, the corresponding {@link #probe(UsbManager, UsbDevice)}
 * method will either return an empty list (device unknown / unsupported) or a
 * singleton list. However, multi-port drivers may return multiple instances.
 *
 * @author mike wakerly (opensource@hoho.com)
 */
public enum UsbSerialProber {
	
	// TODO(mikey): Too much boilerplate.
	
	/**
	 * Prober for {@link FtdiSerialDriver}.
	 *
	 * @see FtdiSerialDriver
	 */
	FTDI_SERIAL {
		@Override
		public boolean isSupported( UsbDevice device ) {
			return (testIfSupported(device, FtdiSerialDriver.getSupportedDevices()));
		}
		@Override
		public UsbSerialDriver getDriver(Context c, UsbDevice device ) {
			UsbDeviceConnection connection = ((UsbManager)c.getSystemService(Context.USB_SERVICE)).openDevice(device);
			if( connection == null )
				return null;
			return( new FtdiSerialDriver(device, connection) );
		}
		@Override
		protected int getId() {
			return 0;
		}
	},
	
	CDC_ACM_SERIAL {
		@Override
		public boolean isSupported( UsbDevice device ) {
			return (testIfSupported(device, CdcAcmSerialDriver.getSupportedDevices()));
		}
		@Override
		public UsbSerialDriver getDriver(Context c, UsbDevice device ) {
			UsbDeviceConnection connection = ((UsbManager)c.getSystemService(Context.USB_SERVICE)).openDevice(device);
			if( connection == null )
				return null;
			return( new CdcAcmSerialDriver(device, connection) );
		}
		@Override
		protected int getId() {
			return 1;
		}
	},
	
	SILAB_SERIAL {
		@Override
		public boolean isSupported( UsbDevice device ) {
			return (testIfSupported(device, Cp2102SerialDriver.getSupportedDevices()));
		}
		@Override
		public UsbSerialDriver getDriver(Context c, UsbDevice device ) {
			UsbDeviceConnection connection = ((UsbManager)c.getSystemService(Context.USB_SERVICE)).openDevice(device);
			if( connection == null )
				return null;
			return( new Cp2102SerialDriver(device, connection) );
		}
		@Override
		protected int getId() {
			return 2;
		}
	};
	

	/**
	 * Tests the supplied {@link UsbDevice} for compatibility with this enum
	 * member, returning one or more driver instances if compatible.
	 *
	 * @param manager the {@link UsbManager} to use
	 * @param usbDevice the raw {@link UsbDevice} to use
	 * @return zero or more {@link UsbSerialDriver}, depending on compatibility
	 * (never {@code null}).
	 */
	protected abstract boolean isSupported(UsbDevice device);
	protected abstract UsbSerialDriver getDriver(Context c, UsbDevice device);
	protected abstract int getId();
	/**
	 * Creates and returns a new {@link UsbSerialDriver} instance for the first
	 * compatible {@link UsbDevice} found on the bus.  If none are found,
	 * returns {@code null}.
	 *
	 * <p/>
	 * The order of devices is undefined, therefore if there are multiple
	 * devices on the bus, the chosen device may not be predictable (clients
	 * should use {@link #findAllDevices(UsbManager)} instead).
	 *
	 * @param usbManager the {@link UsbManager} to use.
	 * @return the first available {@link UsbSerialDriver}, or {@code null} if
	 * none are available.
	 */
	private static String IntentId = "aqUSB";

	public interface Listener {
		public void OnDeviceAvailable( UsbSerialDriver d );
		public void OnNoDeviceAvailable();
	}

	private static Listener mListener;

	public static void findFirstDevice( Context c, Listener l ) {
		mListener = l;
		BroadcastReceiver br = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent i) {
				if( i.getAction().equals(IntentId) ) {
					synchronized( this ) {
						UsbDevice device = (UsbDevice)i.getParcelableExtra( UsbManager.EXTRA_DEVICE );
						int id = i.getIntExtra( "id", -1 );		// the prober being tried
						if( i.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) ) {
							// we have permission to open the device
							// find which prober to use to get the driver
							for( UsbSerialProber prober : UsbSerialProber.values() ) {
								if( prober.getId() == id ) {
									UsbSerialDriver driver = prober.getDriver( context, device );
									if( driver != null )
										mListener.OnDeviceAvailable( driver );
									context.unregisterReceiver( this );
									return;
								}
							}
						}
						// if we get here either permission is denied
						//  or driver could not be created
						context.unregisterReceiver( this );
						mListener.OnNoDeviceAvailable();
					}
				}
			}	
		};
		UsbManager usbManager = (UsbManager)c.getSystemService( Context.USB_SERVICE );
		c.registerReceiver( br, new IntentFilter(IntentId) );
		// The receiver is called with the user's response to the permission dialog
		boolean found = false;
		for( UsbDevice device : usbManager.getDeviceList().values() ) {
			for( UsbSerialProber prober : UsbSerialProber.values() ) {
				if( prober.isSupported(device) ) {
					Intent i = new Intent(IntentId);
					// add the prober id to the intent data so the receiver knows
					//  which prober is being used
					i.putExtra( "id", prober.getId() );
					usbManager.requestPermission( device,
					  PendingIntent.getBroadcast(c, 0, i, PendingIntent.FLAG_UPDATE_CURRENT) );
					found = true;
				}
			}
		}
		if( !found ) {
			c.unregisterReceiver( br );
			l.OnNoDeviceAvailable();
		}
	}


	/**
	 * Returns {@code true} if the given device is found in the driver's
	 * vendor/product map.
	 *
	 * @param usbDevice the device to test
	 * @param supportedDevices map of vendor IDs to product ID(s)
	 * @return {@code true} if supported
	 */
	private static boolean testIfSupported( UsbDevice usbDevice,
	  Map<Integer, int[]> supportedDevices ) {
		int[] supportedProducts = supportedDevices.get( usbDevice.getVendorId() );
		if (supportedProducts == null)
			return false;
		int productId = usbDevice.getProductId();
		for (int supportedProductId : supportedProducts) {
			if (productId == supportedProductId)
				return true;
		}
		return false;
	}

}
